package com.sqlist.shareserver.connection;

import com.sqlist.shareserver.dto.LinkDTO;
import com.sqlist.shareserver.dto.LocationDTO;
import com.sqlist.shareserver.dto.MessageDTO;
import com.sqlist.shareserver.resolver.DataResolverProxy;
import com.sqlist.shareserver.util.Const;
import com.sqlist.shareserver.util.GsonUtils;
import com.sqlist.shareserver.util.LocationMap;
import io.netty.channel.*;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Map;

@Slf4j
@Component
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Autowired
    private DataResolverProxy dataResolverProxy;
    @Autowired
    private LocationMap locationMap;
    @Autowired
    private ConnectionPool connectionPool;

    /**
     * 覆盖了 handlerAdded() 事件处理方法。
     * 每当从服务端收到新的客户端连接时，
     * 客户端的 Channel 存入 ChannelGroup 列表中，
     * 并通知列表中的其他客户端 Channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerAdded");
    }

    /**
     * 覆盖了 handlerRemoved() 事件处理方法。
     * 每当从服务端收到客户端断开时，
     * 客户端的 Channel 自动从 ChannelGroup 列表中移除了，
     * 并通知列表中的其他客户端 Channel
     * @param ctx
     * @throws Exception
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        log.info("handlerRemoved");
    }

    /**
     * 覆盖了 channelRead() 事件处理方法。
     * 每当从服务端读到客户端写入信息时，将信息转发给其他客户端的 Channel。
     * 其中如果你使用的是 Netty 5.x 版本时，需要把 channelRead0() 重命名为messageReceived()
     * @param ctx
     * @param msg
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("接收到客户端发来的消息 {}", msg);
        dataResolverProxy.doAction((String) msg, ctx.channel());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }

    /**
     * 覆盖了 channelActive() 事件处理方法。
     * 服务端监听到客户端活动
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelActive");
    }

    /**
     * 覆盖了 channelInactive() 事件处理方法。
     * 服务端监听到客户端不活动
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("channelInactive");
        sendEveryOne(ctx);
        remove(ctx);
        ctx.close();
    }

    /**
     * exceptionCaught() 事件处理方法是当出现 Throwable 对象才会被调用，
     * 即当 Netty 由于 IO 错误或者处理器在处理事件时抛出的异常时。
     * 在大部分情况下，捕获的异常应该被记录下来并且把关联的 channel 给关闭掉。
     * 然而这个方法的处理方式会在遇到不同异常的情况下有不同的实现，
     * 比如你可能想在关闭连接之前发送一个错误码的响应消息。
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        log.info("exceptionCaught");
        cause.printStackTrace();

        sendEveryOne(ctx);

        LinkDTO linkDTO = new LinkDTO();
        linkDTO.setStatus(LinkDTO.LINK_ERROR);

        MessageDTO<LinkDTO> messageDTO = new MessageDTO<>();
        messageDTO.setType("linkDTO");
        messageDTO.setMsg("连接错误");
        messageDTO.setCode(Const.REQUEST_SUCCESS);
        messageDTO.setData(linkDTO);
        ctx.channel().writeAndFlush(GsonUtils.getInstance().toJson(messageDTO)).addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture channelFuture) throws Exception {
                if (channelFuture.isSuccess()) {
                    remove(ctx);
                    ctx.close();
                }
            }
        });
    }

    // 移除连接信息和位置信息
    public void remove(ChannelHandlerContext ctx) {
        Map<String, Channel> channelMap = connectionPool.getUserMap();
        for (Map.Entry<String, Channel> channelEntry : channelMap.entrySet()) {
            if (channelEntry.getValue() == ctx.channel()) {
                log.info("移除 {} 的连接信息和位置信息", channelEntry.getKey());
                connectionPool.removeChannel(channelEntry.getKey());
                locationMap.removeLocationDTO(channelEntry.getKey());
            }
        }
    }

    // 报告其他人这个下线
    public void sendEveryOne(ChannelHandlerContext ctx) {
        MessageDTO<LocationDTO> locationDTOMessageDTO = new MessageDTO<>();
        locationDTOMessageDTO.setCode(Const.REQUEST);
        locationDTOMessageDTO.setType("locationDTO");
        locationDTOMessageDTO.setData(null);

        log.info("有人下线");
        dataResolverProxy.doAction(GsonUtils.getInstance().toJson(locationDTOMessageDTO), ctx.channel());
    }
}
